package com.opengl.test.opengltest;

//导入OpenGL的静态方法进行存储（静态导入）
import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES20.GL_FLOAT;
import static android.opengl.GLES20.GL_LINEAR;
import static android.opengl.GLES20.GL_LINES;
import static android.opengl.GLES20.GL_POINTS;
import static android.opengl.GLES20.GL_TRIANGLES;
import static android.opengl.GLES20.GL_TRIANGLE_FAN;
import static android.opengl.GLES20.glClear;
import static android.opengl.GLES20.glClearColor;
import static android.opengl.GLES20.glDrawArrays;
import static android.opengl.GLES20.glEnableVertexAttribArray;
import static android.opengl.GLES20.glGetAttribLocation;
import static android.opengl.GLES20.glGetUniformLocation;
import static android.opengl.GLES20.glUniform4f;
import static android.opengl.GLES20.glUniformMatrix4fv;
import static android.opengl.GLES20.glUseProgram;
import static android.opengl.GLES20.glVertexAttribPointer;
import static android.opengl.GLES20.glViewport;
import static android.opengl.Matrix.invertM;
import static android.opengl.Matrix.multiplyMM;
import static android.opengl.Matrix.multiplyMV;
import static android.opengl.Matrix.orthoM;
import static android.opengl.Matrix.rotateM;
import static android.opengl.Matrix.setIdentityM;
import static android.opengl.Matrix.setLookAtM;
import static android.opengl.Matrix.translateM;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.Log;

import com.opengl.test.opengltest.objects.Mallet;
import com.opengl.test.opengltest.objects.Puck;
import com.opengl.test.opengltest.objects.Table;
import com.opengl.test.opengltest.programs.ColorShaderProgram;
import com.opengl.test.opengltest.programs.TextureShaderProgram;
import com.opengl.test.opengltest.util.Geometry;
import com.opengl.test.opengltest.util.LoggerConfig;
import com.opengl.test.opengltest.util.MatrixHelper;
import com.opengl.test.opengltest.util.ShaderHelper;
import com.opengl.test.opengltest.util.TextResourceReader;
import com.opengl.test.opengltest.util.TextureHelper;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

/**
 * Created by Cuckoo322 on 4/9/2017.
 */

public class AirHockeyRenderer implements GLSurfaceView.Renderer {

    private final Context context;

    //模型矩阵
    private final float[] modelMatrix = new float[16];
    //正交投影矩阵
    private final float[] projectionMatrix = new float[16];
    //视图矩阵
    private final float[] viewMatrix = new float[16];
    //缓存投影矩阵 * 视图矩阵的乘积
    private final float[] viewProjectionMatrix = new float[16];
    //缓存投影矩阵 * 视图矩阵 * 模型矩阵的乘积
    private final float[] modelViewProjectionMatrix = new float[16];

    //反转投影矩阵 * 视图矩阵的乘积，通过此矩阵消除投影和视图，回归原来的三维世界
    private final float[] invertedviewProjectionMatrix = new float[16];

    private Table table;
    private Mallet mallet;
    private Puck puck;

    private TextureShaderProgram textureShaderProgram;
    private ColorShaderProgram colorShaderProgram;

    private int texture;

    private final String TAG = "Renderer";

    //定义边界
    private final float leftBound = -0.5f;
    private final float rightBound = 0.5f;
    private final float farBound = -0.8f;
    private final float nearBound = 0.8f;

    //保存木槌是否被按压的状态
    private boolean malletPressed = false;

    //保存蓝色木槌的位置
    private Geometry.Point blueMalletPosition;

    //保存蓝色木槌上一个位置，用于计算木槌移动的速度和方向
    private Geometry.Point previousBlueMalletPosition;

    //保存冰球的位置和速度
    private Geometry.Point puckPosition;
    private Geometry.Vector puckVector;

    //相机移动所在的圆的半径
    private final float viewRadius = 2.2f;
    //相机所在的高度
    private final float viewHeight = 1.2f;
    //相机所在位置的点
    Geometry.Point viewPoint = new Geometry.Point(0f, viewHeight, viewRadius);

    //上一次拖动时的水平移动
    private float previousNormalizedX = 0f;

    public AirHockeyRenderer(Context c) {
        context = c;

    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //定义清屏颜色为最强红色
//        glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
        //定义清屏颜色为黑色
        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

        table = new Table();
        mallet = new Mallet(0.08f, 0.15f, 32);
        puck = new Puck(0.06f, 0.02f, 32);

        textureShaderProgram = new TextureShaderProgram(context);
        colorShaderProgram = new ColorShaderProgram(context);

        texture = TextureHelper.loadTexture(context, R.drawable.air_hockey_surface);

        //初始化蓝色木槌的位置
        blueMalletPosition = new Geometry.Point(0f, mallet.height / 2f, 0.4f);

        //初始化冰球位置
        puckPosition = new Geometry.Point(0f, puck.height / 2f, 0f);
        puckVector = new Geometry.Vector(0f, 0f, 0f);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //设置视口尺寸
        glViewport(0, 0, width, height);

        MatrixHelper.perspectiveM(projectionMatrix, 45, (float) width / (float) height, 1f, 10f);
        setLookAtM(viewMatrix, 0, viewPoint.x, viewPoint.y, viewPoint.z, 0f, 0f, 0f, 0f, 1f, 0f);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //使用已经定义好的颜色进行清屏
        glClear(GL_COLOR_BUFFER_BIT);

        //更新冰球的位置
        puckPosition = puckPosition.translate(puckVector);

        //检查冰球移动是否触界，若触界，反射速度
        if (puckPosition.x < leftBound + puck.radius || puckPosition.x > rightBound - puck.radius) {
            puckVector = new Geometry.Vector(-puckVector.x, puckVector.y, puckVector.z);
        }
        if (puckPosition.z < farBound + puck.radius || puckPosition.z > nearBound - puck.radius) {
            puckVector = new Geometry.Vector(puckVector.x, puckVector.y, -puckVector.z);
        }

        //增加摩擦
        puckVector = puckVector.scale(0.99f);

        //限定冰球的移动范围在桌子内
        puckPosition = new Geometry.Point(
                clamp(puckPosition.x, leftBound + puck.radius, rightBound - puck.radius),
                puckPosition.y,
                clamp(puckPosition.z, farBound + puck.radius, nearBound - puck.radius)
        );

        //更新视图矩阵乘积的结果，结果缓存到viewProjectionMatrix中
        multiplyMM(viewProjectionMatrix, 0, projectionMatrix, 0, viewMatrix, 0);

        //获得反转透视投影矩阵
        invertM(invertedviewProjectionMatrix, 0, viewProjectionMatrix, 0);

        textureShaderProgram.useProgram();

        //绘制桌子
        positionTableInScene();
        textureShaderProgram.setUnitforms(modelViewProjectionMatrix, texture);
        table.bindData(textureShaderProgram);
        table.draw();

        colorShaderProgram.useProgram();

        //绘制冰球
        positionObjectInScene(puckPosition.x, puckPosition.y, puckPosition.z);
        colorShaderProgram.setUnitforms(modelViewProjectionMatrix, 0.8f, 0.8f, 1f);
        puck.bindData(colorShaderProgram);
        puck.draw();

        //绘制红色木槌
        //注意这里由于安卓OpenGL使用左手坐标，z为负值远离观察者
        positionObjectInScene(0f, mallet.height / 2f, -0.4f);
        colorShaderProgram.setUnitforms(modelViewProjectionMatrix, 1f, 0f, 0f);
        mallet.bindData(colorShaderProgram);
        mallet.draw();

        //绘制蓝色木槌
        positionObjectInScene(blueMalletPosition.x, blueMalletPosition.y, blueMalletPosition.z);
        colorShaderProgram.setUnitforms(modelViewProjectionMatrix, 0f, 0f, 1f);
        mallet.draw();

        //计算木槌和冰球之间的距离
        float distance = Geometry.vectorBetween(blueMalletPosition, puckPosition).length();

        //如果木槌和冰球之间的距离小于两者半径，就算冰球碰上木槌
        // 冰球的新速度向量为冰球速度与撞击点夹角的2倍
        if (distance < (mallet.radius + puck.radius)) {
            //木槌与冰球的圆心之间（包含撞击点）向量
            Geometry.Vector centersVector = Geometry.vectorBetween(blueMalletPosition, puckPosition);
            float cosTheta = (centersVector.x * (-puckVector.x) + centersVector.z * (-puckVector.z)) /
                    (centersVector.length() * puckVector.length());
            float theta = (float) Math.acos(cosTheta);
            puckVector = new Geometry.Vector(
                    puckVector.x * (float) Math.cos(2f * theta) - puckVector.z * (float) Math.sin(theta * 2f),
                    puckVector.y,
                    puckVector.x * (float) Math.sin(2f * theta) + puckVector.z * (float) Math.cos(theta * 2f)
            );
        }

    }

    private void positionTableInScene() {
        //使桌子旋转90度置于xz平面
        setIdentityM(modelMatrix, 0);
        rotateM(modelMatrix, 0, -90f, 1f, 0f, 0f);
        multiplyMM(modelViewProjectionMatrix, 0, viewProjectionMatrix, 0, modelMatrix, 0);
    }

    private void positionObjectInScene(float x, float y, float z) {
        setIdentityM(modelMatrix, 0);
        translateM(modelMatrix, 0, x, y, z);
        multiplyMM(modelViewProjectionMatrix, 0, viewProjectionMatrix, 0, modelMatrix, 0);
    }

    public void handleTouchPress(float normalizedX, float normalizedY) {
        if (LoggerConfig.DEBUG_ON) {
            Log.d(TAG, "监听到按下");
        }

        //先获得二维屏幕上触碰位置在三维世界中的射线
        Geometry.Ray ray = convertNormalized2DPointToRay(normalizedX, normalizedY);

        //创建包裹了木槌的虚拟圆球，用于测试触碰点的射线是否与木槌相交
        Geometry.Sphere malletBoundingSphere = new Geometry.Sphere(
                new Geometry.Point(
                        blueMalletPosition.x,
                        blueMalletPosition.y,
                        blueMalletPosition.z
                ),
                mallet.height / 2f
        );

        //测试触碰点射线是否与木槌相交
        malletPressed = Geometry.intersects(malletBoundingSphere, ray);

        if (malletPressed && LoggerConfig.DEBUG_ON) {
            Log.d(TAG, "点击到蓝色木槌上了");
        }

        //初始化拖动的水平移动
        previousNormalizedX = normalizedX;
    }

    public void handleTouchDrag(float normalizedX, float normalizedY) {
        if (LoggerConfig.DEBUG_ON) {
            Log.d(TAG, "监听到拖动");
        }

        //保存蓝色木槌上一个位置，用于计算木槌移动的速度和方向
        previousBlueMalletPosition = blueMalletPosition;

        if (malletPressed) {
            Geometry.Ray ray = convertNormalized2DPointToRay(normalizedX, normalizedY);

            //定义代表桌面的平面
            Geometry.Plane plane = new Geometry.Plane(new Geometry.Point(0f, 0f, 0f), new Geometry.Vector(0f, 1f, 0f));

            //找到触控射线与桌面相交的点
            Geometry.Point touchedPoint = Geometry.intersectionPoint(ray, plane);

            //保存这个点的位置，作为蓝色木槌的新位置
            // 注意这里由于安卓OpenGL使用左手坐标，z为负值远离观察者
            blueMalletPosition = new Geometry.Point(
                    clamp(touchedPoint.x, leftBound + mallet.radius, rightBound - mallet.radius),
                    mallet.height / 2f,
                    clamp(touchedPoint.z, 0f + mallet.radius, nearBound - mallet.radius)
            );

            //计算木槌和冰球之间的距离
            float distance = Geometry.vectorBetween(blueMalletPosition, puckPosition).length();

            //如果木槌和冰球之间的距离小于两者半径，就算木槌碰上冰球
            // 冰球的新速度向量等于木槌移动的速度向量 + 冰球原有的速度向量
            if (distance < (mallet.radius + puck.radius)) {
                Geometry.Vector malletVector = Geometry.vectorBetween(previousBlueMalletPosition, blueMalletPosition);
                puckVector = puckVector.add(malletVector);
            }
        } else {
            float dragDistance = 2f * (normalizedX - previousNormalizedX);
            previousNormalizedX = normalizedX;

            //将相机沿圆周移动，theta是拖动距离对应的转动角度，R是圆周的半径
            // 公式推导：x = R * sin alpha, z = R * cos alpha, x' = R * sin(theta + alpha), z = R * cos(theta + alpha)
            // 通过拆解sin(a+b)=sin a*cos b+cos a*sin b以及cos(a+b) = cos a * cos b - sin a * sin b
            // 以及联合前面的4个条件，可以推出 x' = z * sin theta + x * cos theta, z' = z * cos theta - x * sin theta
            float theta = - dragDistance / viewRadius;
            viewPoint = new Geometry.Point(
                    (viewPoint.z * (float) Math.sin(theta)) + (viewPoint.x * (float) Math.cos(theta)),
                    viewPoint.y,
                    (viewPoint.z * (float) Math.cos(theta)) - (viewPoint.x * (float) Math.sin(theta))
            );
//            Log.d(TAG, "drag distance: "+dragDistance);
//            Log.d(TAG, "view point: ("+viewPoint.x+", "+viewPoint.z+")");
//            Log.d(TAG, "view radius: "+Math.sqrt(viewPoint.x*viewPoint.x+viewPoint.z*viewPoint.z));
            setLookAtM(viewMatrix, 0, viewPoint.x, viewPoint.y, viewPoint.z, 0f, 0f, 0f, 0f, 1f, 0f);
        }
    }

    private Geometry.Ray convertNormalized2DPointToRay(float nornalizedX, float normalizedY) {
        //初始先在视锥体的近端平面和远端平面各选择一个点作为射线上的2个点
        //这2个点是触控点在近端平面上的归一化坐标和远端平面上的归一化坐标
        // 第4分量w未定，暂定为1，需要通过反转透视投影矩阵相乘获得w值
        final float[] nearPointNdc = {nornalizedX, normalizedY, -1, 1};
        final float[] farPointNdc = {nornalizedX, normalizedY, 1, 1};

        //获得w值的在触摸射线上的点，该点是在正常三维世界中的点
        final float[] nearPointWorld = new float[4];
        final float[] farPointWorld = new float[4];

        multiplyMV(nearPointWorld, 0, invertedviewProjectionMatrix, 0, nearPointNdc, 0);
        multiplyMV(farPointWorld, 0, invertedviewProjectionMatrix, 0, farPointNdc, 0);

        divideByW(nearPointWorld);
        divideByW(farPointWorld);

        Geometry.Point nearPointRay = new Geometry.Point(nearPointWorld[0], nearPointWorld[1], nearPointWorld[2]);
        Geometry.Point farPointRay = new Geometry.Point(farPointWorld[0], farPointWorld[1], farPointWorld[2]);

        return new Geometry.Ray(nearPointRay, Geometry.vectorBetween(nearPointRay, farPointRay));
    }

    /**
     * 将参数向量中前3个分量分别除以第4分量中的“反转w”，以消除透视投影效果
     * clamp即钳，将返回值钳在min和max之间
     * @param vector 含有4个分量的点
     */
    private void divideByW(float[] vector) {
        vector[0] /= vector[3];
        vector[1] /= vector[3];
        vector[2] /= vector[3];
    }

    /**
     * value与可取值的范围的比较，返回大于min小于max内的value值，否则返回min或max
     * @param value
     * @param min
     * @param max
     * @return
     */
    public float clamp(float value, float min, float max) {
        return Math.min(max, Math.max(min, value));
    }
}
